package com.zmn.mcc.cas.server;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.zmn.base.common.data.common.dro.company.CompanyContactDRO;
import com.zmn.base.common.data.dubbo.interfaces.company.CompanyListRemoteService;
import com.zmn.common.constant.StatusConsts;
import com.zmn.common.dictionary.GlobalDict;
import com.zmn.common.dto2.ResponseDTO;
import com.zmn.common.utils.date.DateUtil;
import com.zmn.common.utils.number.NumberUtil;
import com.zmn.common.utils.string.StringUtil;
import com.zmn.mcc.business.interfaces.staff.StaffBService;
import com.zmn.mcc.cache.StaffCache;
import com.zmn.mcc.cache.SysCache;
import com.zmn.mcc.cas.core.ShiroTokenManager;
import com.zmn.mcc.cas.core.utils.CasUtils;
import com.zmn.mcc.cas.model.CasSysDRO;
import com.zmn.mcc.cas.model.LoginStaff;
import com.zmn.mcc.cas.model.UserTokenDO;
import com.zmn.mcc.common.dto.permit.PermitLitDO;
import com.zmn.mcc.common.dto.permit.SpermitDTO;
import com.zmn.mcc.common.dto.staff.LoginStaffDO;
import com.zmn.mcc.model.entity.log.LoginLogStaff;
import com.zmn.mcc.model.entity.organize.McOrganize;
import com.zmn.mcc.model.entity.permit.McPermit;
import com.zmn.mcc.model.entity.permit.McPermitQuery;
import com.zmn.mcc.model.entity.staff.McStaff;
import com.zmn.mcc.model.entity.staff.McStaffQuery;
import com.zmn.mcc.model.entity.sys.McSys;
import com.zmn.mcc.services.interfaces.log.LoginLogStaffService;
import com.zmn.mcc.services.interfaces.organize.OrganizeService;
import com.zmn.mcc.services.interfaces.permit.PermitService;
import com.zmn.mcc.services.interfaces.role.RolePermitService;
import com.zmn.mcc.services.interfaces.staff.StaffService;
import com.zmn.mcc.services.interfaces.sys.SysService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.shiro.session.Session;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @author quanjic
 * @version v1.0
 * @since 2020/6/16 11:16
 **/
@Service
@Slf4j
public class CasServerStaffServiceImpl implements CasServerStaffService, EnvironmentAware {

    @Resource
    private StaffService staffService;

    @Resource
    private RolePermitService rolePermitService;

    @Resource
    private PermitService permitService;

    @DubboReference(version = com.zmn.base.plat.engine.common.constant.DubboConsts.INTERFACE_VERSION, check = false)
    private CompanyListRemoteService companyListRemoteService;

    @Resource
    private SysService sysService;

    @Resource
    private OrganizeService organizeService;

    @Resource
    private LoginLogStaffService loginLogStaffService;

    @Resource
    private ShiroTokenManager shiroTokenManager;

    @Resource
    private StaffCache staffCache;

    @Resource
    private SysCache sysCache;

    @Override
    public Integer getCityIdByCompanyId(Integer companyId) {
        if (companyId == null) {
            throw new NullPointerException("null companyId");
        }
        ResponseDTO<CompanyContactDRO> companyDROResponseDTO = companyListRemoteService.getCompanyContactDROById(companyId);
        if (companyDROResponseDTO.isSuccess()) {
            return companyDROResponseDTO.getData().getCityId();
        }
        return null;
    }

    /**
     * 带有角色与数据权限关联字段
     *
     * @param staff
     * @return
     */
    @Override
    public LoginStaff getLoginStaffDOByMcStaff(McStaff staff) {
        LoginStaffDO staffDO = staffBService.getLoginStaffByMcStaff(staff);
        if (staffDO == null) {
            return null;
        }
        LoginStaff result = new LoginStaff();
        BeanUtils.copyProperties(staffDO, result);
        return result;
    }

    @Override
    public List<Integer> listAvailableRoleIdsByStaffId(Integer staffId) {
        if (staffId == null) {
            throw new NullPointerException("null staffId");
        }
        McStaff staff = doQueryStaffByStaffId(staffId);
        if (staff == null) {
            return Collections.EMPTY_LIST;
        }
        return staffBService.listRoleByStaff(staff.getStaffId(), staff.getDeptId(), staff.getPermitSelf() != null ? staff.getPermitSelf() : null);
    }


//    @Override
//    @Deprecated
//    public List<McPermit> listPermitByRoleIds(List<Integer> roleIds) {
//        // TODO
//        return Collections.EMPTY_LIST;
//    }

    @Override
    @Deprecated
    public List<McPermit> listPermitByStaffId(Integer staffId) {
        if (staffId == null) {
            throw new NullPointerException("null staffId");
        }
        List<Integer> idList = rolePermitService.listPermitIdByStaffId(staffId);
        if (CollectionUtils.isNotEmpty(idList)) {
            McPermitQuery query = new McPermitQuery();
            query.setRoleIds(idList);
            return permitService.listByQuery(query);
        }
        return Collections.EMPTY_LIST;
    }

    @Autowired
    private StaffBService staffBService;


    @Override
    public List<PermitLitDO> listPermitOfCurrentAppByStaffId(LoginStaff staff) {

        if (staff == null) {
            throw new NullPointerException("null staff");
        }

        SpermitDTO dto = staffBService.listPermitByLoginStaff(staff, getSysId());

        return dto.isSuccess() ? dto.getItems() : Collections.EMPTY_LIST;
    }


    @Override
    public ResponseDTO<McStaff> checkPassword(String staffName, String password) {
        McStaff staff = null;
        try {
            if (StringUtils.isEmpty(staffName) || StringUtils.isEmpty(password)) {
                return ResponseDTO.fail(StatusConsts.FAIL_LOGIN_USERPWD_EMPTY, "用户名与密码不能为空");
            }

            staff = this.doQueryStaff2(staffName);

            if (staff == null) {
                return ResponseDTO.fail(StatusConsts.ERROR_NOTUSER, "用户不存在");
            }

            if (StringUtils.isEmpty(staff.getPassword())) {
                return ResponseDTO.fail(StatusConsts.ERROR_ADMIN_LOGIN_NO, "用户未开通后台登录权限");
            }

            if (staff.getStatus() != GlobalDict.STATUS_ENABLE) {
                return ResponseDTO.fail(StatusConsts.FAIL_LOGIN_DISABLE, "非正常状态");
            }

            if (!checkPwd(password, staff)) {
                log.warn("#cas#login 密码错误,错误的明文；staffName:{}, password:{}", staffName, password);
                return ResponseDTO.fail(StatusConsts.FAIL_LOGIN_PASSWORD, "密码错误");
            }
//            if (doOrgan(staff)) {
//                return ResponseDTO.success(staff);
//            }
        } catch (Exception e) {
            log.error("#cas#login 登录失败；staffName:{}, password:{}", staffName, password, e);
            return ResponseDTO.fail(StatusConsts.ERROR_UNKNOWN, e.getMessage());
        }

        return ResponseDTO.success(staff);
    }

    @Override
    public ResponseDTO<McStaff> checkDTalkUserId(String staffName, String dTalkUserId) {
        McStaff staff = null;
        try {
            if (StringUtils.isEmpty(staffName) || StringUtils.isEmpty(dTalkUserId)) {
                return ResponseDTO.fail(StatusConsts.FAIL_LOGIN_USERPWD_EMPTY, "钉钉userId不能为空");
            }

            staff = this.doQueryStaff2(staffName);

            if (staff == null) {
                return ResponseDTO.fail(StatusConsts.ERROR_NOTUSER, "用户不存在");
            }

            if (StringUtils.isEmpty(staff.getPassword())) {
                return ResponseDTO.fail(StatusConsts.ERROR_ADMIN_LOGIN_NO, "用户未开通后台登录权限");
            }

            if (staff.getStatus() != GlobalDict.STATUS_ENABLE) {
                return ResponseDTO.fail(StatusConsts.FAIL_LOGIN_DISABLE, "非正常状态");
            }


        } catch (Exception e) {
            log.error("#cas#login 登录失败；staffName:{}, dTalkUserId:{}", staffName, dTalkUserId, e);
            return ResponseDTO.fail(StatusConsts.ERROR_UNKNOWN, e.getMessage());
        }
        return ResponseDTO.success(staff);
    }


    @Override
    public void updateLastLoginTime(Integer staffId) {
        if (staffId == null) {
            throw new NullPointerException("null staffId");
        }
        staffService.updateLastLoginTime(staffId);
    }


    @Override
    public void updatePassword(Integer staffId, String password, String operatorUserName) {
        if (staffId == null || StringUtils.isEmpty(password)) {
            throw new NullPointerException("null staffId or password");
        }
        staffService.updatePassword(staffId, password, operatorUserName);
    }

    @Override
    public void updateDTalkId(McStaff staff) {
        staffService.updateDTalkId(staff);
    }

    @Override
    @Deprecated
    public void saveLoginLog(int loginType, int loginStatus, int statusCode, String statusMessage) {

        Session session = shiroTokenManager.getSession();
        if (session == null) {
            return;
        }

        UserTokenDO token = shiroTokenManager.getUserToken();
        if (token == null) {
            return;
        }

        String staffName = token.getUserKey();

        this.saveLoginLog(session, staffName, loginType, loginStatus, statusCode, statusMessage);

    }

    @Override
    public List<CasSysDRO> listApp() {
        List<McSys> list = sysCache.listApp();
        List<CasSysDRO> result = new ArrayList<>(list.size());
        for (McSys sys : list) {
            result.add(new CasSysDRO(sys.getSysId(), sys.getSysName(), sys.getUrl()));
        }
        return result;
    }

    /**
     * 记录登录/登出/踢出日志
     *
     * @param session       指定的Session信息
     * @param loginName     登录/登出/被踢出人的登录名
     * @param loginType     登录/登出/踢出 1登录,2登出,3踢出
     * @param loginStatus   登录结果 1失败,2成功
     * @param statusCode    结果代码
     * @param statusMessage 结果消息
     */
    private void saveLoginLog(Session session, String loginName, int loginType, int loginStatus, int statusCode, String statusMessage) {

        if (session == null) {
            return;
        }

        LoginLogStaff log = new LoginLogStaff();

        log.setSessionId(session.getId().toString());
        log.setLoginName(loginName);
        log.setLoginType(loginType);
        log.setLoginHost(session.getHost());
        log.setLoginIp(parseIp(session.getAttribute("ip") != null ? session.getAttribute("ip").toString() : ""));
        // 地图坐标
        log.setLoginLatlng("");
        // 地址, 根据地图坐标或IP解析
        log.setLoginAddr("");
        log.setLoginStatus(loginStatus);
        log.setLoginTime(DateUtil.getNow());
        log.setDeviceType(session.getAttribute("deviceType") != null ? session.getAttribute("deviceType").toString() : "");
        log.setDeviceCode(session.getAttribute("deviceCode") != null ? session.getAttribute("deviceCode").toString() : "");
        log.setStatusCode(statusCode);
        log.setStatusMessage(statusMessage);

        log.setOperator(log.getLoginName());

        loginLogStaffService.insert(log);
    }

    /**
     * 临时密码
     */
//    static final String TEMPLATE_PWD = SystemConsts.CACHE_STAFF_TEMP_PWD;

    private PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

//    private String getStaffTempPwdKey(Integer staffId) {
//
//        return  String.format(TEMPLATE_PWD, staffId);
//    }

    private Boolean comparePassword(String pwd, String dbPwd, String salt) {
        return checkPassword(pwd, salt, dbPwd);
    }

    /**
     * 密码验证：先验证临时密码
     *
     * @param password 未加密输入密码
     * @param staff
     * @return
     */
    private boolean checkPwd(String password, McStaff staff) {
        // 真实密码
        if (staff.getPassword() != null && this.comparePassword(password, staff.getPassword(), staff.getSalt())) {
            return true;
        } else {
            // 临时密码
            String redisPwd = staffCache.getTempPwd(staff.getStaffId());
            if (redisPwd != null && redisPwd.equals(password)) {
                log.info("#cas#login 使用临时密码登录成功，staffName:{}", staff.getStaffName());
                return true;
            }
        }
        return false;
    }

    private Boolean checkPassword(String password, String salt, String hashPassword) {
        if (StringUtil.isBlank(password) || StringUtil.isBlank(salt) || StringUtil.isBlank(hashPassword)) {
            return false;
        }
        String saltPassword = getSaltPassword(password, salt);
        return passwordEncoder.matches(saltPassword, hashPassword);
    }

    /**
     * 拼接盐密码
     *
     * @param password 未加密的原始密码
     * @param salt     盐值
     * @return
     */
    private String getSaltPassword(String password, String salt) {
        return new StringBuffer(password).append(":").append(salt).toString();
    }

    /**
     * 添加组织架构
     *
     * @param staff
     * @return
     */
    private boolean doOrgan(McStaff staff) {
        if (NumberUtil.isPositiveInteger(staff.getOrgId())) {
            McOrganize org = organizeService.findByKey(staff.getOrgId());
            if (org == null) {
                return true;
            }
            // TODO 未知用处
//            staff.setOrgLevel(org.getLevel());
//            KvDTO<Integer, String> orgs = organService.findParentOrganLine(staff.getOrgId());
//            KvDTO<Integer, String> organLinked = findParentOrganLinkedList(staff.getOrgId());
//            staff.setOrgans(orgs);
        }
        return false;
    }

    private String parseIp(String str) {
        if (StringUtils.isEmpty(str)) {
            return str;
        }
        return str.split(",")[0];
    }


//    private Cache<Integer, KvDTO<Integer, String>> cache = CacheBuilder.newBuilder().expireAfterWrite(1, TimeUnit.HOURS).maximumSize(10000).build();

    /**
     * 查询指定组织架构的父级链表 TODO 暂时保留
     *
     * @param organId
     * @return
     */
//    private KvDTO<Integer, String> findParentOrganLinkedList(Integer organId) {
//
//        KvDTO<Integer, String> cacheValue = cache.getIfPresent(organId);
//        if (cacheValue != null) {
//            return cacheValue;
//        }
//
//        McOrganize organize = organizeService.findByKey(organId);
//        if (organize == null) {
//            return new KvDTO<>();
//        }
//        KvDTO<Integer, String> root = new KvDTO(organize.getOrgId(), organize.getOrgName());
//        McOrganize current = organize;
//
//        int depth = 5;
//        while (current != null && current.getParentId() != null && current.getParentId() != GlobalConsts.TOP_ID && depth-- > 0) {
//            McOrganize parentOrga = organizeService.findByKey(current.getParentId());
//            if (parentOrga == null) {
//                break;
//            }
//            KvDTO parent = new KvDTO(parentOrga.getOrgId(), parentOrga.getOrgName());
//            parent.addChildren(root);
//            root = parent;
//        }
//
//        cache.put(organId, root);
//
//        return root;
//    }


//    private String doGetOrganizeById(Integer organId){
//        ;
//    }


    private Cache<String, McStaff> staffGuavaCache = CacheBuilder.newBuilder().maximumSize(10000).expireAfterWrite(5, TimeUnit.MINUTES).build();


    private String getStaffCacheKey(String staffName) {
        return "local:cas:staff:" + staffName;
    }

    private String getStaffIdCacheKey(Integer staffId) {
        return "local:cas:staffId:" + staffId;
    }

    @Override
    public McStaff queryStaff(String staffNameOrMobile) {
        if (StringUtils.isEmpty(staffNameOrMobile)) {
            throw new NullPointerException("null staffNameOrMobile");
        }
        return doQueryStaff2(staffNameOrMobile);
    }

    @Override
    public McStaff queryStaffByDTalkId(String dingTalkUserId) {
        if (StringUtils.isEmpty(dingTalkUserId)) {
            throw new NullPointerException("null dingTalkUserId");
        }
        return doQueryStaff3(dingTalkUserId);
    }

    private McStaff doQueryStaff2(String staffNameOrMobile) {
        // 不使用缓存
        McStaffQuery query = new McStaffQuery();
        query.setStaffName(staffNameOrMobile);
        McStaff forLogin = staffService.findForLogin(query);

        if (Objects.nonNull(forLogin)) {
            return forLogin;
        }

        if (StringUtil.isMobile(staffNameOrMobile)) {
            query = new McStaffQuery();
            query.setMobile(staffNameOrMobile);
            return staffService.findForLogin(query);
        } else {
            return null;
        }
    }

    private McStaff doQueryStaff3(String dingTalkUserId) {
        McStaffQuery query = new McStaffQuery();
        query.setDTalkId(dingTalkUserId);
        McStaff forLogin = staffService.findForLogin(query);
        if (Objects.nonNull(forLogin)) {
            return forLogin;
        }
        return null;
    }

    private McStaff doQueryStaffFromCache(String staffNameOrMobile) {
        // 使用缓存
        String key = getStaffCacheKey(staffNameOrMobile);
        McStaff staff = staffGuavaCache.getIfPresent(key);
        if (staff != null) {
            return staff;
        }

        McStaffQuery query = new McStaffQuery();
        query.setStaffName(staffNameOrMobile);
        if (StringUtil.isMobile(staffNameOrMobile)) {
            query.setMobile(staffNameOrMobile);
        } else {
            query.setStaffName(staffNameOrMobile);
        }

        staff = staffService.findForLogin(query);
        if (staff != null) {
            staffGuavaCache.put(key, staff);
            staffGuavaCache.put(getStaffIdCacheKey(staff.getStaffId()), staff);
        }

        return staff;
    }

    private McStaff doQueryStaffByStaffId(Integer staffId) {

        String key = getStaffIdCacheKey(staffId);
        McStaff staff = staffGuavaCache.getIfPresent(key);
        if (staff != null) {
            return staff;
        }

        staff = staffService.findByKey(staffId);
        if (staff != null) {
            staffGuavaCache.put(key, staff);
            staffGuavaCache.put(getStaffCacheKey(staff.getStaffName()), staff);
        }

        return staff;
    }

    private Integer sysId;

    private String applicationName;

    private Integer getSysId() {
        if (sysId != null) {
            return sysId;
        }
        sysId = CasUtils.findSysId(applicationName, listApp());
        return sysId;
    }

    /**
     * Set the {@code Environment} that this component runs in.
     *
     * @param environment
     */
    @Override
    public void setEnvironment(Environment environment) {
        this.applicationName = environment.getProperty("spring.application.name");
    }
}
